package cn.ycc1.functionlibrary.reflection;

/**
 * Reading and Writing Fields
 * @author ycc
 * @date 2025/3/9
 * A Field object let you get more information on the corresponding field: its type and its modifiers, and enables you to get
 *  the value of this field for a given object, and to set it. This section also covers how you can discover fields in a class.
 */
public class Fields {
    /**
     * Locating Field
     * There are two categories of methods provided in Class for accessing fields.
     *
     * First, you can look for a specific field. These methods suppose you have a name for the field you are looking for.
     * Second, you can look for all the fields that are declared in this class, or for the fields declared in this class,
     * and all its super classes, up to the Object class. In the first case, you will get all the fields: public, protected,
     * default (package) access, and private. In the second case, you will get only the public fields.
     * The following tables provide a summary of all the member-locating methods and their characteristics.
     *
     * Class API	Array of fields?	Inherited fields?	Private fields?
     * getDeclaredField(String)	no	no	yes
     * getField(String)	no	yes	no
     * getDeclaredFields()	yes	no	yes
     * getFields()	yes	yes	no
     * Let us see these methods in action on the ArrayList class from the JDK.
     *
     * First, let us print all the declared fields of this class.
     *
     * Class<?> arrayListClass = Class.forName("java.util.ArrayList");
     * Field[] fields = arrayListClass.getDeclaredFields();
     * for (Field field : fields) {
     *     boolean isPublic = field.accessFlags().contains(AccessFlag.PUBLIC);
     *     System.out.println("Field: " + field.getName() +
     *                        " declared in " + field.getDeclaringClass().getSimpleName() +
     *                        " " + field.accessFlags());
     * }
     * Running this code prints the following.
     *
     * Field: serialVersionUID declared in ArrayList [PRIVATE, STATIC, FINAL]
     * Field: DEFAULT_CAPACITY declared in ArrayList [PRIVATE, STATIC, FINAL]
     * Field: EMPTY_ELEMENTDATA declared in ArrayList [PRIVATE, STATIC, FINAL]
     * Field: DEFAULTCAPACITY_EMPTY_ELEMENTDATA declared in ArrayList [PRIVATE, STATIC, FINAL]
     * Field: elementData declared in ArrayList [TRANSIENT]
     * Field: size declared in ArrayList [PRIVATE]
     * As you can see, only the fields from the ArrayList class itself have been found, whatever their modifier is.
     *
     * You can try the same code with the Field.getFields() method.
     *
     * Class<?> arrayListClass = Class.forName("java.util.ArrayList");
     * Field[] fields = arrayListClass.getFields();
     * System.out.println("Public fields for ArrayList: " + fields.length);
     * Running this code prints the following.
     *
     * Public fields for ArrayList: 0
     * Indeed, there is no public fields in ArrayList, nor in any of its super classes.
     */

    /**
     * Obtaining Field Types
     * There are two ways to get information on the type of a field. The first and easiest one is to call its getType() method,
     * that returns a Class object. This works well as long as the type of the field is not generic. If it is, then the type is
     * just Object, which may give you ambiguous information.
     *
     * Obtaining Non-Generic Field Types
     * The first way to get the type of a field is to use the getType() method, that returns a Class object.
     *
     * Suppose that you have the following class.
     *
     * class User {
     *     private String name;
     * }
     * Then you can get the type of the name field with the following code.
     *
     * Class<?> userClass = User.class;
     * Field nameField = userClass.getDeclaredField("name");
     * Class<?> type = nameField.getType();
     * System.out.println("type = " + type.getName());
     * Running this code prints the following.
     *
     * type = java.lang.String
     * Obtaining Generic Field Types
     * The previous method of getting the type of a field works as long as this type is not a generic type.
     *
     * Suppose that you now have the following generic class.
     *
     * class Box<T> {
     *     private T t;
     * }
     * And you look for the type of the field using the classical way.
     *
     * Class<?> boxClass = Box.class;
     * Field field = boxClass.getDeclaredField("t");
     * Class<?> type = field.getType();
     * System.out.println("type = " + type.getName());
     * What you get is the erased type, which is Object.
     *
     * type = java.lang.Object
     * This is why, starting with Java SE 5, the Field.getGenericType() method has been added to the Field class, that returns
     * a Type object. This object has a single method getTypeName() that gives you the name of the generic type of this field.
     *
     * Let us replace the previous code by the following.
     *
     * Class<?> boxClass = Box.class;
     * Field field = boxClass.getDeclaredField("t");
     * Type genericType = field.getGenericType();
     * System.out.println("type = " + genericType.getTypeName());
     * Running the previous code now prints the following.
     *
     * type = T
     * The exact type of the t field is now preserved, as it is declared in the class.
     *
     * Note that this method cannot give you access to the real type at runtime, it can only give you the type you declared in
     * the source code. You can see that on the following example. Suppose that you have the following Box class.
     *
     * public static class Box<T> {
     *     private final T t;
     *
     *     public Box(T t) {
     *         this.t = t;
     *     }
     * }
     * And the following code.
     *
     * Box<String> box = new Box<>("Hello");
     * Class<?> boxClass = box.getClass();
     * Field tField = boxClass.getDeclaredField("t");
     * Type type = tField.getGenericType();
     * System.out.println("type = " + type);
     * Running the previous example prints the following.
     *
     * type = T
     */

    /**
     * Retrieving Field Modifiers
     * The modifiers of a field work in the same way as the modifiers of a class. The method Field.getModifiers() returns an
     * int in which each bit represent a modifier. For instance, if the bit 0 is set, it means that this field is public.
     * If the bit 3 is set, then this field is static. You can also use the static methods of the Modifier class to decode
     * this integer.
     *
     * Suppose you have the following User class:
     *
     * class User {
     *     private String name;
     * }
     * You can get the modifiers of the field name with the following code:
     *
     * Class<?> userClass = User.class;
     * Field nameField = userClass.getDeclaredField("name");
     *
     * int modifiers = nameField.getModifiers();
     * System.out.println("isStatic:  " + Modifier.isStatic(modifiers));
     * System.out.println("isPublic:  " + Modifier.isPublic(modifiers));
     * System.out.println("isPrivate: " + Modifier.isPrivate(modifiers));
     * System.out.println("isFinal:   " + Modifier.isFinal(modifiers));
     * This code prints the following.
     *
     * isStatic:  false
     * isPublic:  false
     * isPrivate: true
     * isFinal:   false
     * Note that you can also use Field.accessFlags() in the same way as you did on classes. You can see this method in action
     * on the following example.
     *
     * Class<?> userClass = User.class;
     * Field nameField = userClass.getDeclaredField("name");
     * Set<AccessFlag> flags = nameField.accessFlags();
     * System.out.println("Flags = " + flags);
     * Running the previous code prints the following.
     *
     * Flags = [PRIVATE]
     * Also, remember that there is not a one to one correspondence between modifiers and access flags. You can read more on
     * access flags in this section
     */

    /**
     * Getting and Setting Field Values
     * The Reflection API allows you to read and update the value of fields through the use of Field objects. This is done
     * through the use of two methods: Field.get(Object) and Field.set(Object,Object).
     *
     * In both cases, you need to pass the instance you need to read or write, because the Field object is obtained from the
     * Class, which is unique for a given class, and thus shared for all the instances of this class. Whatever method you use
     * to get a reference to a specific class, you always get the same instance of Class. So the instance of the Field class
     * you get for a given field of a given class is also unique.
     *
     * Let us use the following class for our example.
     *
     * class User {
     *     String name;
     *
     *     public User(String name) {
     *         this.name = name;
     *     }
     *
     *     public String toString() {
     *         return "User[name='" + name + "]";
     *     }
     * }
     * You can get the value of the name class for a given instance of this User class with the following code.
     *
     * User maria = new User("Maria");
     * Class<?> userClass = maria.getClass();
     * Field nameField = userClass.getDeclaredField("name");
     * String name = nameField.get(maria);
     * System.out.println("name of maria = " + name);
     * Running this code prints the following:
     *
     * name of maria = Maria
     * You can also update the name field with the following code.
     *
     * nameField.set(maria, "Mary");
     * Dealing With Non-Visible Fields
     * You can notice that the field name from the User class is not private. The example we show can be run as long as the
     * code that is calling nameField.set(maria, "Mary") can see the field name, which is visible from the same package.
     *
     * If you make this field private, or if you move the calling code to another package, making the field name not visible
     * from the calling code, you will get the following exception.
     *
     * Exception in thread "main" java.lang.IllegalAccessException: class org.devjava.Main cannot access
     * a member of class org.devjava.User with modifiers "private"
     *     at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:420)
     *     at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:709)
     *     at java.base/java.lang.reflect.Field.checkAccess(Field.java:1140)
     *     at java.base/java.lang.reflect.Field.get(Field.java:425)
     *     at org.devjava.Main.main(Main.java:16)
     * This exception tells you that the visibility rules are still holding for the Reflection API.
     *
     * You can still get access to a private field, by telling the API that you want to suppress the checks for access control.
     * This can be done with the following code.
     *
     * nameField.setAccessible(true);
     * nameField.set(maria, "Mary");
     * This codes makes the field nameField accessible, despite being private. A common mistake is to think that this call to
     * setAccessible(true) makes a field non-private, which is not the case.
     *
     * Note that there are restrictions on the members you can make accessible, if there are in other modules. You can check
     * the page Reflective Access with Open Modules and Open Packages for more information.
     *
     * Dealing With Final Fields
     * The reading of final fields works the same as the reading of any field.
     *
     * As you saw in the previous section, making a field accessible suppresses the checks for access control. That includes
     * visibility and final modifiers.
     *
     * Suppose that you now have this class, where the name field has been declared private and final.
     *
     * class User {
     *     private final String name;
     *
     *     public User(String name) {
     *         this.name = name;
     *     }
     *
     *     public String toString() {
     *         return "User[name='" + name + "]";
     *     }
     * }
     * Now consider the following code. You are getting a field object for the name field of the User class, and you use it to
     * update the value of the name field. Note the call to nameField.setAccessible(true).
     *
     * User maria = new User("Maria");
     * System.out.println("Maria = " + maria);
     *
     * Class<?> userClass = maria.getClass();
     * Field nameField = userClass.getDeclaredField("name");
     * nameField.setAccessible(true);
     * nameField.set(maria, "Mary");
     * System.out.println("Maria = " + maria);
     * Running the previous code prints the following. Despite the name field being private and final, you could update it
     * using the Reflection API.
     *
     * Maria = User[name=Maria]
     * Maria = User[name=Mary]
     * Setting the accessible flag to true on a field object gives you read and write access to this field. There are some
     * exceptions though: it does not give you write access if the field is a static field, and it does not give you write
     * access on the instance fields of a record.
     */

    /**
     * Dealing with Primitive Types
     * As you know, primitive types can be automatically boxed to their wrapper types. You need to keep in mind that the
     * Field.get(Object) returns an Object type, and that Field.set(Object,Object) methods takes an Object type. So when you
     * are reading a field from an object using reflection, you need to cast the object you get to its exact type. Is your
     * field has a primitive type or a wrapper type, you can cast the result of a call to Field.get(Object) to the
     * corresponding primitive type or wrapper type.
     *
     * When the type of the field you are working with is a primitive type, then you can avoid the overhead of boxing and
     * unboxing this primitive type to its corresponding wrapper type by using one of the methods Field.getInt(Object) or
     * Field.setInt(Object,int). There are methods for all the primitive types on the Field class, for both getting and
     * setting the value of the field.
     *
     * Be careful though, using such a method on a field that is not of the right type will throw an exception. Consider the
     * following example, where we have added an age field of type int.
     *
     * public class User {
     *     private String name;
     *     private int age;
     *
     *     public User(String name, int age) {
     *         this.name = name;
     *         this.age = age;
     *     }
     * }
     * You can run the following code.
     *
     * User maria = new User("Maria", 32);
     * Class<?> userClass = User.class;
     * Field ageField = userClass.getDeclaredField("age");
     *
     * int age = ageField.getInt(maria);
     * System.out.println("age = " + age);
     *
     * ageField.setInt(maria, 33);
     * age = ageField.getInt(maria);
     * System.out.println("age = " + age);
     * This codes prints the following:
     *
     * age = 32
     * age = 33
     * But if you change the type of the age field from int to Integer, then running the same code will produce the following
     * exception.
     *
     * Exception in thread "main" java.lang.IllegalArgumentException:
     * Attempt to get java.lang.Integer field "org.devjava.User.age" with illegal data type conversion to int
     *     at java.base/jdk.internal.reflect.FieldAccessorImpl.newGetIllegalArgumentException(FieldAccessorImpl.java:130)
     *     at java.base/jdk.internal.reflect.FieldAccessorImpl.newGetIntIllegalArgumentException(FieldAccessorImpl.java:193)
     *     at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.getInt(MethodHandleObjectFieldAccessorImpl.java:84)
     *     at java.base/java.lang.reflect.Field.getInt(Field.java:612)
     *     at org.devjava.Main.main(Main.java:40)
     */
}
